﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using Creventive.Wpf.ShellFactory.Utility;
using Creventive.Wpf.ShellFactory.Workspace.Primitives;

namespace Creventive.Wpf.ShellFactory.Workspace
{
    /// <summary>
    ///   Provides a workspace where views can be presented as windows and dialogs.
    /// </summary>
    public static class WindowWorkspace
    {
        #region Static Fields/Constants

        /// <summary>
        /// Backing field for the SizeToContent dependency property
        /// </summary>
        public static readonly DependencyProperty SizeToContentProperty = DependencyProperty.RegisterAttached("SizeToContent", typeof(SizeToContent), typeof(WindowWorkspace), new UIPropertyMetadata(SizeToContent.WidthAndHeight));
        /// <summary>
        /// Backing field for the ResizeMode dependency property
        /// </summary>
        public static readonly DependencyProperty ResizeModeProperty = DependencyProperty.RegisterAttached("ResizeMode", typeof(ResizeMode?), typeof(WindowWorkspace), new UIPropertyMetadata(null));
        /// <summary>
        /// Backing field for the Topmost dependency property
        /// </summary>
        public static readonly DependencyProperty TopmostProperty = DependencyProperty.RegisterAttached("Topmost", typeof(bool), typeof(WindowWorkspace), new UIPropertyMetadata(false));
        /// <summary>
        /// Backing field for the ShowInTaskbar dependency property
        /// </summary>
        public static readonly DependencyProperty ShowInTaskbarProperty = DependencyProperty.RegisterAttached("ShowInTaskbar", typeof(bool?), typeof(WindowWorkspace), new UIPropertyMetadata(null));
        /// <summary>
        /// Backing field for the WindowStartupLocation dependency property
        /// </summary>
        public static readonly DependencyProperty WindowStartupLocationProperty = DependencyProperty.RegisterAttached("WindowStartupLocation", typeof(WindowStartupLocation?), typeof(WindowWorkspace), new UIPropertyMetadata(null));
        /// <summary>
        /// Backing field for the WindowStyle dependency property
        /// </summary>
        public static readonly DependencyProperty WindowStyleProperty = DependencyProperty.RegisterAttached("WindowStyle", typeof(WindowStyle?), typeof(WindowWorkspace), new UIPropertyMetadata(null));
        /// <summary>
        /// Backing field for the WindowState dependency property
        /// </summary>
        public static readonly DependencyProperty WindowStateProperty = DependencyProperty.RegisterAttached("WindowState", typeof(WindowState), typeof(WindowWorkspace), new FrameworkPropertyMetadata(WindowState.Normal, (s, e) => NotifyWindowStateChanged((ApplicationView)s, (WindowState)e.OldValue, (WindowState)e.NewValue)));

        private static readonly PropertyDescriptor TitlePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ApplicationView.TitleProperty, typeof(ApplicationView));
        private static readonly PropertyDescriptor IconPropertyDescriptor = DependencyPropertyDescriptor.FromProperty(ApplicationView.IconProperty, typeof(ApplicationView));

        private static Dictionary<ApplicationView, ApplicationViewContainerWindow> windowLookupTable = new Dictionary<ApplicationView, ApplicationViewContainerWindow>();

        #endregion

        /// <summary>
        ///   Gets the state of the window.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static WindowState GetWindowState(ApplicationView obj)
        {
            return (WindowState)obj.GetValue(WindowStateProperty);
        }

        /// <summary>
        ///   Sets the state of the window.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetWindowState(ApplicationView obj, WindowState value)
        {
            obj.SetValue(WindowStateProperty, value);
        }

        /// <summary>
        ///   Gets the window startup location.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static WindowStartupLocation? GetWindowStartupLocation(ApplicationView obj)
        {
            return (WindowStartupLocation?)obj.GetValue(WindowStartupLocationProperty);
        }

        /// <summary>
        ///   Sets the window startup location.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetWindowStartupLocation(ApplicationView obj, WindowStartupLocation? value)
        {
            obj.SetValue(WindowStartupLocationProperty, value);
        }

        /// <summary>
        ///   Gets the window style.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static WindowStyle? GetWindowStyle(ApplicationView obj)
        {
            return (WindowStyle?)obj.GetValue(WindowStyleProperty);
        }

        /// <summary>
        ///   Sets the window style.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetWindowStyle(ApplicationView obj, WindowStyle? value)
        {
            obj.SetValue(WindowStyleProperty, value);
        }

        /// <summary>
        ///   Gets whether a window should be resizeable.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static ResizeMode? GetResizeMode(ApplicationView obj)
        {
            return (ResizeMode?)obj.GetValue(ResizeModeProperty);
        }

        /// <summary>
        ///   Sets whether a window should be resizeable.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetResizeMode(ApplicationView obj, ResizeMode? value)
        {
            obj.SetValue(ResizeModeProperty, value);
        }

        /// <summary>
        ///   Gets a value determining if a window should always be kept on top.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static bool GetTopmost(ApplicationView obj)
        {
            return (bool)obj.GetValue(TopmostProperty);
        }

        /// <summary>
        ///   Sets a value determining if a window should always be kept on top.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">if set to <c>true</c>, the window will be kept on top.</param>
        public static void SetTopmost(ApplicationView obj, bool value)
        {
            obj.SetValue(TopmostProperty, value);
        }

        /// <summary>
        ///   Gets a value determining if a window will be shown in the OS taskbar.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static bool? GetShowInTaskbar(ApplicationView obj)
        {
            return (bool?)obj.GetValue(ShowInTaskbarProperty);
        }

        /// <summary>
        ///   Sets a value determining if a window will be shown in the OS taskbar.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetShowInTaskbar(ApplicationView obj, bool? value)
        {
            obj.SetValue(ShowInTaskbarProperty, value);
        }

        /// <summary>
        ///   Gets how a window will size itself in relation to the view.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <returns></returns>
        public static SizeToContent GetSizeToContent(ApplicationView obj)
        {
            return (SizeToContent)obj.GetValue(SizeToContentProperty);
        }

        /// <summary>
        ///   Sets how a window will size itself in relation to the view.
        /// </summary>
        /// <param name = "obj">The obj.</param>
        /// <param name = "value">The value.</param>
        public static void SetSizeToContent(ApplicationView obj, SizeToContent value)
        {
            obj.SetValue(SizeToContentProperty, value);
        }

        private static void NotifyTitleChanged(object sender, EventArgs e)
        {
            ApplicationView view = (ApplicationView)sender;
            ApplicationViewContainerWindow window;
            if (windowLookupTable.TryGetValue(view, out window))
                window.Title = view.Title.ToStringSafe();
        }

        private static void NotifyIconChanged(object sender, EventArgs e)
        {
            ApplicationView view = (ApplicationView)sender;
            ApplicationViewContainerWindow window;
            if (windowLookupTable.TryGetValue(view, out window))
                window.Icon = view.Icon;
        }

        private static void NotifyWindowStateChanged(ApplicationView sender, WindowState oldValue, WindowState newValue)
        {
            ApplicationViewContainerWindow window;
            if (windowLookupTable.TryGetValue(sender, out window))
                window.WindowState = newValue;
        }

        /// <summary>
        ///   Shows the specified view in a window.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "ownerElement">The owner element.</param>
        public static void Show(ApplicationView view, FrameworkElement ownerElement = null)
        {
            if (view == null)
                throw new ArgumentNullException("view");
            ShowCore(ownerElement, view, false);
        }

        /// <summary>
        ///   Shows the specified view in a window.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "view">The view.</param>
        /// <param name = "ownerElement">The owner element.</param>
        public static void Show<T>(ApplicationView<T> view, FrameworkElement ownerElement = null) where T : class
        {
            if (view == null)
                throw new ArgumentNullException("view");
            ShowCore(ownerElement, view, false);
        }

        /// <summary>
        ///   Shows the specified view as a modal dialog.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "ownerElement">The owner element.</param>
        /// <returns></returns>
        public static bool? ShowDialog(ApplicationView view, FrameworkElement ownerElement = null)
        {
            if (view == null)
                throw new ArgumentNullException("view");
            return ShowCore(ownerElement, view, true);
        }

        /// <summary>
        ///   Shows the specified view as a modal dialog.
        /// </summary>
        /// <typeparam name = "T"></typeparam>
        /// <param name = "view">The view.</param>
        /// <param name = "ownerElement">The owner element.</param>
        /// <returns></returns>
        public static bool? ShowDialog<T>(ApplicationView<T> view, FrameworkElement ownerElement = null) where T : class
        {
            if (view == null)
                throw new ArgumentNullException("view");
            return ShowCore(ownerElement, view, true);
        }

        private static bool? ShowCore(FrameworkElement ownerElement, ApplicationView view, bool asDialog)
        {
            // TODO: Find a way to style the windows
            view.Closed += view_Closed;
            TitlePropertyDescriptor.AddValueChanged(view, NotifyTitleChanged);
            IconPropertyDescriptor.AddValueChanged(view, NotifyIconChanged);

            ApplicationViewContainerWindow containerWindow = new ApplicationViewContainerWindow();
            windowLookupTable[view] = containerWindow;
            containerWindow.StateChanged += containerWindow_StateChanged;
            containerWindow.Closed += containerWindow_Closed;
            containerWindow.Source = new ApplicationViewSource(view);
            containerWindow.Title = view.Title.ToStringSafe();
            containerWindow.Icon = view.Icon;
            containerWindow.SizeToContent = GetSizeToContent(view);
            containerWindow.Topmost = GetTopmost(view);
            bool? showInTaskbar = GetShowInTaskbar(view);
            if (showInTaskbar != null)
                containerWindow.ShowInTaskbar = showInTaskbar.Value;
            WindowStyle? windowStyle = GetWindowStyle(view);
            if (windowStyle != null)
                containerWindow.WindowStyle = windowStyle.Value;

            ResizeMode? resizeMode = GetResizeMode(view);
            WindowStartupLocation? windowStartupLocation = GetWindowStartupLocation(view);
            if (asDialog)
            {
                var ownerWindow = (ownerElement != null) ? ownerElement.FindTopLevelElement() as Window : null;
                if (ownerWindow != null)
                {
                    containerWindow.Owner = ownerWindow;
                    containerWindow.WindowStartupLocation = windowStartupLocation ?? WindowStartupLocation.CenterOwner;
                }
                else
                    containerWindow.WindowStartupLocation = windowStartupLocation ?? WindowStartupLocation.CenterScreen;

                containerWindow.ResizeMode = resizeMode ?? ResizeMode.NoResize;
                return containerWindow.ShowDialog();
            }
            else
            {
                containerWindow.Show();
                return true;
            }
        }

        private static void containerWindow_Closed(object sender, EventArgs e)
        {
            var containerWindow = (ApplicationViewContainerWindow)sender;
            containerWindow.StateChanged -= containerWindow_StateChanged;
            containerWindow.Closed -= containerWindow_Closed;
        }

        private static void containerWindow_StateChanged(object sender, EventArgs e)
        {
            var containerWindow = (ApplicationViewContainerWindow)sender;
            var view = containerWindow.View;
            if (view != null)
                SetWindowState(view, containerWindow.WindowState);
        }

        private static void view_Closed(object sender, EventArgs e)
        {
            var view = (ApplicationView)sender;
            IconPropertyDescriptor.RemoveValueChanged(view, NotifyIconChanged);
            TitlePropertyDescriptor.RemoveValueChanged(view, NotifyTitleChanged);
            view.Closed -= view_Closed;
            windowLookupTable.Remove(view);
        }

        /// <summary>
        ///   Closes the specified view.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <param name = "result">The result.</param>
        public static void Close(ApplicationView view, bool? result)
        {
            ApplicationViewContainerWindow window;
            if (windowLookupTable.TryGetValue(view, out window))
            {
                window.DialogResult = result;
                window.Close();
            }
        }
    }
}